import pandas as pd
import requests
import json
import time
import logging
import random
import numpy as np
from datetime import datetime

import multiprocessing
from threading import Thread


logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)


def ConvertToSimTime_us(start_time, time_ratio, day, running_time):
    return (time.time() - start_time - (day - 1) * running_time) * time_ratio


class BotsClass:
    def __init__(self, username, password):
        self.username = username
        self.password = password

    def login(self):
        pass

    def init(self):
        pass

    def bod(self):
        pass

    def work(self):
        pass

    def eod(self):
        pass

    def final(self):
        pass


class DataManager:
    def __init__(self, api, stock_code):
        self.api = api
        self.stock = stock_code
        self.tick_data = np.zeros((14400, 44))

    def get_tick_data(self, LOB):
        tmp_ls = [LOB["lob"]["localtime"], LOB["lob"]["last_price"], LOB["lob"]["trade_value"],
                  LOB["lob"]["trade_volume"]]
        tmp_ls = (tmp_ls + LOB["lob"]["bidprice"] + LOB["lob"]["askprice"] +
                  LOB["lob"]["bidvolume"] + LOB["lob"]["askvolume"])
        return tmp_ls

    def UpdateTickData(self):
        """
        :param stock_code: "UBIQ023"
        :return:
        """
        LOB = self.api.sendGetLimitOrderBook(bot.token_ub, self.stock)
        localtime = LOB["lob"]["localtime"]
        self.tick_data[localtime, :] = np.array(self.get_tick_data(LOB))
        start_localtime = localtime
        while True:
            LOB = self.api.sendGetLimitOrderBook(bot.token_ub, self.stock)
            if LOB["status"] == "Success" and localtime != LOB["lob"]["localtime"]:
                localtime = LOB["lob"]["localtime"]
                self.tick_data[localtime, :] = np.array(self.get_tick_data(LOB))
            if localtime >= 3899:
                end_localtime = localtime
                data = pd.DataFrame(self.tick_data)
                data.to_csv(f"{self.stock}.csv")
                non_zero_counts = data.iloc[:, -1].astype(bool).sum(axis=0)
                logger.info(f"{self.stock}数据已存储到csv, 共{end_localtime-start_localtime+1}个数据，"
                            f"获取到{non_zero_counts}个数据")
                break
            time.sleep(0.01)


class BotsDemoClass(BotsClass):

    def __init__(self, username, password, target_stocks: list):
        """
        login & init: 在每个自然日比赛开始前或者交易所运行期间启动程序，系统将自动执行login函数以连接交易所，并调用init函数进行该自然日比赛的初始化
        """
        super().__init__(username, password)
        self.api = InterfaceClass("https://trading.competition.ubiquant.com")
        self.target_stocks = target_stocks
        self.tick_data = dict()
        self.process = dict()

    def login(self):
        response = self.api.sendLogin(self.username, self.password)
        if response["status"] == "Success":
            self.token_ub = response["token_ub"]
            logger.info("Login Success: {}".format(self.token_ub))
        else:
            logger.info("Login Error: ", response["status"])

    def GetInstruments(self):
        response = self.api.sendGetInstrumentInfo(self.token_ub)
        if response["status"] == "Success":
            self.instruments = []
            for instrument in response["instruments"]:
                self.instruments.append(instrument["instrument_name"])
            logger.info("Get Instruments: {}".format(self.instruments))

    def init(self):
        """
        login & init: 在每个自然日比赛开始前或者交易所运行期间启动程序，系统将自动执行login函数以连接交易所，并调用init函数进行该自然日比赛的初始化
        """
        response = self.api.sendGetGameInfo(self.token_ub)
        if response["status"] == "Success":
            self.start_time = response["next_game_start_time"]
            self.running_days = response["next_game_running_days"]
            self.running_time = response["next_game_running_time"]
            self.time_ratio = response["next_game_time_ratio"]
        self.GetInstruments()
        self.day = 0
        self.instruments_information = [[] for _ in range(29)]

        for stock in self.target_stocks:
            self.tick_data[stock] = DataManager(self.api, stock)

    def bod(self):
        """
        bod: 在每个交易日开始的时候会调用这个函数，用于每天交易策略的初始化。
        """
        for stock in self.target_stocks:
            tmp_process = Thread(target=self.UpdateTickData, name=stock, args=(self.tick_data[stock], ))
            self.process[stock] = tmp_process
            tmp_process.start()

    def work(self):
        """
        work: 你程序化交易策略的实现。每次调用你可进行查询并处理行情、下单、撤单等操作
        """
        stockID = random.randint(0, len(self.instruments) - 1)
        LOB = self.api.sendGetLimitOrderBook(self.token_ub, self.instruments[stockID])
        if LOB["status"] == "Success":
            askprice_1 = float(LOB["lob"]["askprice"][0])
            t = ConvertToSimTime_us(self.start_time, self.time_ratio, self.day, self.running_time)
            response = self.api.sendOrder(self.token_ub, self.instruments[stockID], t, "buy", askprice_1, 100)

        return {""}

    def eod(self):
        """
        eod: 在每个交易日结束的时候会调用这个函数，你可以用于每天交易结束时执行一些操作。
        """
        pass

    def final(self):
        """
        final: 在每个自然日比赛结束的时候会调用这个函数，你可以在比赛结束后自己做一些处理工作，以供分析。
        """
        pass

    def UpdateTickData(self, datamanager):
        datamanager.UpdateTickData()


class InterfaceClass:
    def __init__(self, domain_name):
        """
        :param domain_name: "https://trading.competition.ubiquant.com"
        """
        self.domain_name = domain_name
        self.session = requests.Session()

    def sendLogin(self, username, password):
        """
        Login:登录
        使用response[ "token_ub"]获取你的令牌，需要使用令牌来调用下面的各个函数
        """
        url = self.domain_name + "/api/Login"
        data = {
            "user": username,
            "password": password
        }
        response = self.session.post(url, data=json.dumps(data)).json()
        return response

    def sendOrder(self, token_ub, instrument, localtime, direction, price, volume):
        """
        Order: 下单
        原则上你需要保证你的下单合法（具体参见交易细则与交易限制)。
        使用response["index"]获取这次下单的索引，你需要使用索引来进行撤单。
        """
        logger.debug(
            "Order: Instrument: {}, Direction:{}, Price: {}, Volume:{}".format(instrument, direction, price, volume))
        url = self.domain_name + "/api/TradeAPI/Order"
        data = {
            "token_ub": token_ub,
            "user_info": "NULL",
            "instrument": instrument,
            "localtime": localtime,
            "direction": direction,
            "price": price,
            "volume": volume,
        }
        response = self.session.post(url, data=json.dumps(data)).json()
        return response

    def sendCancel(self, token_ub, instrument, localtime, index):
        """
        Cancel:撤单
        """
        logger.debug("Cancel: Instrument: {}, index:{}".format(instrument, index))
        url = self.domain_name + "/api/TradeAPI/Cancel"
        data = {
            "token_ub": token_ub,
            "user_info": "NULL",
            "instrument": instrument,
            "localtime": 0,
            "index": index
        }
        response = self.session.post(url, data=json.dumps(data)).json()
        return response

    def sendGetLimitOrderBook(self, token_ub, instrument):
        """
        GetLimitOrderBook:获取限价订单簿
        使用response["lob"]["limit_up_price"], response]["lob"]["limit_down_price"]获取涨跌停价
        使用response["lob"]["bidprice"], response["lob"]["askprice"], response["lob"]["bidvolume"],
           response["lob"]["askvolume "]获取买卖价量的列表
        使用response["lob"]["last_price"], response["lob"]["trade_volume"],
           response["lob"]["trade_value"]获取最后成交价，成交量和成交额
        """
        logger.debug("GetLimitOrderBOok: Instrument: {}".format(instrument))
        url = self.domain_name + "/api/TradeAPI/GetLimitOrderBook"
        data = {
            "token_ub": token_ub,
            "instrument": instrument
        }
        response = self.session.post(url, data=json.dumps(data)).json()
        return response

    def sendGetUserInfo(self, token_ub):
        """
        GetUserInfo: 获取用户信息
        使用response["pnl"], response["sharpe"], response["orders"], response["error_orders"],
           response["order_value"], response["trade_value"], response["commision"],
           response["totaposition"], response["remain_funds"]
           获取PNL，Sharpe，总下单数，错单数，总下单金额，总成交金额，手续费，市值，可用资金
        使用response["rows"]获取股票的信息列表
        在response["rows"][i]中，
        使用instrument_name, share_holding, position, pnl, orders, error_orders, order_value, trade_value, commision
        获得股票名称，持仓，市值，PNL，下单数，错单数，总下单金额，交金额，手续费
        """
        logger.debug("GetUserInfo: ")
        url = self.domain_name + "/api/TradeAPI/GetUserInfo"
        data = {
            "token_ub": token_ub,
        }
        response = self.session.post(url, data=json.dumps(data)).json()
        return response

    def sendGetGameInfo(self, token_ub):
        """
        GetGameInfo:获取比赛信息
        使用response["next_game_start_time"], response["next_game_running_days"],
           response["next_game_running_time"], response["next_game_time_ratio"}获取比赛的相关信息
        """
        logger.debug("GetGameInfo: ")
        url = self.domain_name + "/api/TradeAPI/GetGameInfo"
        data = {
            "token_ub": token_ub,
        }
        response = self.session.post(url, data=json.dumps(data)).json()
        return response

    def sendGetInstrumentInfo(self, token_ub):
        """
        GetInstrumentInfo: 获取股票信息
        使用response["instruments"]获取股票的列表
        对于每一只股票, response["instruments"][i]["instrument_name"]获取其对应的股票名称
        使用response["instrument_number"]获取股票的数量
        """
        logger.debug("GetInstrumentInfo: ")
        url = self.domain_name + "/api/TradeAPI/GetInstrumentInfo"
        data = {
            "token_ub": token_ub,
        }
        response = self.session.post(url, data=json.dumps(data)).json()
        return response

    def sendGetTrade(self, token_ub, instrument):
        """
        GetTrade: 获取成交信息
        使用response["trade_list"]获取成交列表
        使用response["trade_list"][i]中trade_time, trade_index, order_index, trade_price, trade_volume, remain_volume
        获取成交时间，成交索引，下单索引，成交价，成交量，剩余量
        """
        logger.debug("GetTrade: Instrment: {}".format(instrument))
        url = self.domain_name + "/api/TradeAPI/GetTrade"
        data = {
            "token_ub": token_ub,
            "instrument_name": instrument
        }
        response = self.session.post(url, data=json.dumps(data)).json()
        return response

    def sendGetActiveOrder(self, token_ub):
        """
        GetActiveOrder:获取当前外挂单
        使用response["instruments"]获取当前外挂的股票列表
        使用response["instruments"][i]["instrument"]获取股票名称，
           response["instruments"][i]["active_orders"]获取当前股票的外挂单列表。
        在response["instruments"][i]["active_orders"][j]
        使用order_index,order_price, volume, direction 获取下单索引，下单价，下单量，下单方向
        """
        logger.debug("GetActiveOrder: ")
        url = self.domain_name + "/api/TradeAPI/GetActiveOrder"
        data = {
            "token_ub": token_ub,
        }
        response = self.session.post(url, data=json.dumps(data)).json()
        return response


if __name__ == "__main__":
    ls = ["UBIQ000", "UBIQ001", "UBIQ002", "UBIQ003", "UBIQ004", "UBIQ005",
          "UBIQ006", "UBIQ007", "UBIQ008", "UBIQ009"] + ["UBIQ0"+str(i) for i in range(10, 29)]
    bot = BotsDemoClass("UBIQ_TEAM080", "J9lc1nOzo", ls)
    bot.login()
    bot.init()
    print(f"start_time: {bot.start_time, datetime.fromtimestamp(bot.start_time).strftime('%Y-%m-%d %H:%M:%S')},"
          f"time_ratio: {bot.time_ratio}, "
          f"day: {bot.day},"
          f"running_time: {bot.running_time}, "
          f"running_days: {bot.running_days}")
    print(ConvertToSimTime_us(bot.start_time, bot.time_ratio, bot.day, bot.running_time))

    SimTimeLen = 14400
    endWaitTime = 300
    while True:
        if ConvertToSimTime_us(bot.start_time, bot.time_ratio, bot.day, bot.running_time) < SimTimeLen:
            break
        else:
            bot.day += 1
    print(f"day: {bot.day}, time: {ConvertToSimTime_us(bot.start_time, bot.time_ratio, bot.day, bot.running_time)}")
    bot.bod()

    time.sleep(100)


# while bot.day <= bot.running_days:
#     while True:
#         if ConvertToSimTime_us(bot.start_time, bot.time_ratio, bot.day, bot.running_time) > -900:
#             break
#     bot.bod()
#     now = round(ConvertToSimTime_us(bot.start_time, bot.time_ratio, bot.day, bot.running_time))
#     for s in range(now, SimTimeLen + endWaitTime):
#         while True:
#             if ConvertToSimTime_us(bot.start_time, bot.time_ratio, bot.day, bot.running_time) >= s:
#                 break
#         t = ConvertToSimTime_us(bot.start_time, bot.time_ratio, bot.day, bot.running_time)
#         logger.info("Work Time: {}".format(t))
#         # if t < SimTimeLen - 30:
#         #     instruments_information = bot.get_instruments_information()
#             # bot.work()
#     bot.eod()
#     bot.day += 1
# bot.final()

